package org.hawk.gwt.ppc.reflect;

import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import org.hawk.gwt.ppc.Configuration;
import org.hawk.gwt.ppc.GWTModule;
import org.hawk.gwt.ppc.Log;
import org.hawk.gwt.ppc.javacc.JavaParserTreeConstants;
import org.hawk.gwt.ppc.javacc.JavaScanner;
import org.hawk.gwt.ppc.javacc.ParseException;
import org.hawk.gwt.ppc.reflect.lang.ClassDelegate;
import org.hawk.gwt.ppc.reflect.lang.ClassLoaderDelegate;
import org.hawk.gwt.ppc.scanner.ProcessingUnit;
import org.hawk.gwt.ppc.utils.IO;
import org.hawk.gwt.ppc.utils.Name;
import org.hawk.gwt.ppc.utils.Pattern;

/**
 * Facade for all operations necessary for reflection-related
 * preprocessing work.
 * @author alex.bereznevatiy@gmail.com
 */
public class ReflectionProcessingUnit implements ProcessingUnit {
	private static final String CLASS_PROPERTY = "class";
	private static final String CLASS_DELEGATE_PROPERTY = "$class";
	private MetadataProcessor metadataProcessor;
	private ReflectionCallsProcessor reflectionCallsProcessor;
	private List<Pattern> pattern;
	
	public ReflectionProcessingUnit(){
		metadataProcessor = new MetadataProcessor();
		reflectionCallsProcessor = new ReflectionCallsProcessor();
	}
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.scanner.Preprocessor#configure(org.hawk.gwt.ppc.Configuration, org.hawk.gwt.ppc.javacc.JavaScanner)
	 */
	public void configure(Configuration config,JavaScanner scanner) {
		configurePattern(config);
		configureMetadataProcessor(scanner);
		configureReflectionCallsProcessor(scanner);
	}
	private void configureReflectionCallsProcessor(JavaScanner scanner) {
		scanner.addProcessor(JavaParserTreeConstants.JJTPRIMARYSUFFIX,
				reflectionCallsProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTNAME,
				reflectionCallsProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTFIELDDECLARATION,
				reflectionCallsProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTTYPE,
				reflectionCallsProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTPRIMARYEXPRESSION,
				reflectionCallsProcessor);
	}
	private void configureMetadataProcessor(JavaScanner scanner) {
		scanner.addProcessor(JavaParserTreeConstants.JJTCOMPILATIONUNIT,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTCLASSORINTERFACEBODY,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTEXTENDSLIST,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTPACKAGEDECLARATION,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTIMPLEMENTSLIST,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTFIELDDECLARATION,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTCONSTRUCTORDECLARATION,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTMETHODDECLARATOR,
				metadataProcessor);
		scanner.addProcessor(JavaParserTreeConstants.JJTIMPORTDECLARATION,
				metadataProcessor);
	}
	
	private void configurePattern(Configuration config) {
		String[] patterns = config.get(Configuration.REFLECTION_PARAMETER,"**").split(",");
		pattern = new LinkedList<Pattern>();
		for(String p : patterns){
			pattern.add(config.getNewPattern(p));
		}
		pattern = Collections.unmodifiableList(pattern);
	}
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.scanner.Preprocessor#processModule(org.hawk.gwt.ppc.GWTModule)
	 */
	public void processModule(GWTModule module) throws IOException, ParseException {
		module.addInheritance("org.hawk.gwt.ppc.reflect.lang");
	}
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.scanner.Preprocessor#processEntryPoint(java.lang.String, java.io.Reader, java.io.StringWriter)
	 */
	public void processEntryPoint(String entryPoint, 
			Reader reader,Writer writer) throws IOException {
		String pointContent = IO.toString(reader);
		int index = pointContent.lastIndexOf('}');
		if(index<0)return;
		if(Log.isDebugEnabled()){
			logReflectionClassesForThisModule(entryPoint);
		}
		writer.write(pointContent,0, index);
		writer.write(createClassLoaderDefinition());
		writer.write(pointContent,index,pointContent.length()-index);
		metadataProcessor.clear();
	}
	/*
	 * (non-Javadoc)
	 * @see org.hawk.gwt.ppc.scanner.Preprocessor#acceptResource(java.lang.String)
	 */
	public boolean acceptResource(String resource) {
		for(Pattern p : pattern){
			if(p.match(resource)){
				return true;
			}
		}
		return false;
	}
	private String createClassLoaderDefinition() {
		final String ClassDelegate = ClassDelegate.class.getName();
		return "static {\n" +
			ClassDelegate+".registerClassLoader(\n"+
			"new "+ClassLoaderDelegate.class.getName()+"(){\n" +
			"protected "+ClassDelegate+"<?>[] getClasses(){\n"+
			"return new "+ClassDelegate+"[]{\n" +
			getCollectedClassesAsArrayDefinition()+
			"};\n}\n"+//end of getClasses() method
			"protected String getPackage(){\n" +
			"return \""+getCommonPackage()+"\";" +
			"}\n"+//end of getPackage() method
			"protected Class<?>[] getNotReflectableClasses(){\n"+
			"return new Class[]{\n" +
			getImportedClassesAsArrayDefinition()+
			"};\n}\n"+//end of getNotReflectableClasses() method
			"});\n" +//end of ClassLoaderDelegate inner class
			"}\n";//end of static
	}
	
	private String getCollectedClassesAsArrayDefinition() {
		return toArrayDefinition(
				metadataProcessor.getTopClasses(),
				CLASS_DELEGATE_PROPERTY);
	}
	
	private String getImportedClassesAsArrayDefinition() {
		Set<String> result = new HashSet<String>();
		for(String className : metadataProcessor.getTopClasses()){
			result.addAll(metadataProcessor.getImports(className));
		}
		result.removeAll(metadataProcessor.getTopClasses());
		return toArrayDefinition(result,CLASS_PROPERTY);
	}
	
	private String toArrayDefinition(Collection<String> classes,String property) {
		Iterator<String> iterator = classes.iterator();
		if(!iterator.hasNext())return "";
		StringBuilder result = new StringBuilder();
		appendArrayItem(property, iterator, result);
		while(iterator.hasNext()){
			result.append(',');
			appendArrayItem(property, iterator, result);
		}
		return result.toString();
	}
	
	private void appendArrayItem(String property, Iterator<String> iterator,
			StringBuilder result) {
		result.append(iterator.next());
		result.append('.');
		result.append(property);
	}
	
	private String getCommonPackage() {
		Iterator<String> iterator = metadataProcessor.getTopClasses().iterator();
		if(!iterator.hasNext())return "";
		String packageName = Name.getParentJavaName(iterator.next());
		while(iterator.hasNext()){
			packageName = Name.getCommonPackage(iterator.next(),packageName);
		}
		return packageName;
	}
	
	private void logReflectionClassesForThisModule(String entryPoint) {
		Log.debug("\tLinking classes to entry point: "+entryPoint);
		for(String cls : metadataProcessor.getTopClasses()){
			Log.debug("\t\tLinking "+cls);
		}
	}
}
